﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;

namespace KBEngine.Common
{
    public class Machines
    {
        UdpClient _udp_socket;
        UdpClient _udp_broadcast_socket;

        readonly int _uid;
        readonly ushort _listenPort;
        readonly string _userName;

        public Dictionary<ComponentType, List<ComponentInfo>> interfaces;
        /// <summary>
        /// key:machineID  value:ComponentInfo
        /// </summary>
        public Dictionary<uint, List<ComponentInfo>> interfaces_groups;
        /// <summary>
        /// key:machineID  value:uid
        /// </summary>
        public Dictionary<uint, List<int>> interfaces_groups_uid;
        public List<ComponentInfo> machines;
        private Dictionary<ComponentType, int> ct2gus;
        private Dictionary<ComponentType, int> ct2cid;
        private string _fireReceive;

        public Machines(int uid = -1, string username = null, ushort listenPort = 0)
        {
            _listenPort = listenPort;
            _uid = uid;
            _userName = username;
            _fireReceive = $"__{nameof(Machines)}_{nameof(__FireReceive)}_{Define.getObjAddres(this)}";

            if (_uid < 0)
                _uid = Define.getDefaultUID();
            if (string.IsNullOrEmpty(_userName))
                _userName = System.Environment.UserName;
            startListen();
            reset();
        }

        public void startListen()
        {
            StopListen();
            Event.registerIn(_fireReceive, this, nameof(__FireReceive));

            //_udp_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Unspecified);
            _udp_socket = new UdpClient();
            _udp_socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
            _udp_socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 5 * 1024 * 1024);
            _udp_socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000);
            EndPoint endPoint = new IPEndPoint(IPAddress.Any, _listenPort);
            _udp_socket.Client.Bind(endPoint);
        }
        private void reset()
        {
            interfaces = new Dictionary<ComponentType, List<ComponentInfo>>();
            interfaces_groups = new Dictionary<uint, List<ComponentInfo>>();
            interfaces_groups_uid = new Dictionary<uint, List<int>>();
            machines = new List<ComponentInfo>();
        }
        public void StopListen()
        {
            if (_udp_socket == null)
                return;
            _udp_socket.Close();
            _udp_socket = null;
            Event.deregisterIn(_fireReceive, this, nameof(__FireReceive));
        }

        private EndPoint GetBroadcastEndPoint(string ip = null)
        {
            if (_udp_broadcast_socket == null)
            {
                _udp_broadcast_socket = new UdpClient(AddressFamily.InterNetwork);
                //_udp_broadcast_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Unspecified);
                _udp_broadcast_socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
                _udp_broadcast_socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
            }
            _udp_broadcast_socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, string.IsNullOrEmpty(ip) ? 1 : 0);
            if (string.IsNullOrEmpty(ip))
                ip = "255.255.255.255";
            return new IPEndPoint(IPAddress.Parse(ip), Define.MACHINPS_PORT);
        }

        public void QueryAllInterfaces(string ip = null, int tryCount = 1, int timeout = 1000)
        {
            reset();
            var bundle = Bundle.createObject();
            bundle.newMessage(FixedMsg.Get("Machine::onQueryAllInterfaceInfos"));
            bundle.writeInt32(_uid);
            bundle.writeString(_userName);
            bundle.writeUint16((ushort)IPAddress.HostToNetworkOrder((short)_listenPort));

            var endPoint = GetBroadcastEndPoint(ip);
            bundle.sendUDP(_udp_broadcast_socket.Client, endPoint);
            ListenReceive(tryCount, timeout, (memList) =>
            {
                parseQueryDatas(memList);
                Event.fireOut(MachineEvent.OnQueryAllInterfaces);
            });
        }
        public void QueryMachines(string ip = null, int tryCount = 1, int timeout = 1000)
        {
            reset();
            var bundle = Bundle.createObject();
            bundle.newMessage(FixedMsg.Get("Machine::onQueryMachines"));
            bundle.writeInt32(_uid);
            bundle.writeString(_userName);
            bundle.writeUint16((ushort)IPAddress.HostToNetworkOrder((short)_listenPort));

            var endPoint = GetBroadcastEndPoint(ip);
            bundle.sendUDP(_udp_broadcast_socket.Client, endPoint);
            ListenReceive(tryCount, timeout, (memList) =>
            {
                parseQueryDatas(memList);
                Event.fireOut(MachineEvent.OnQueryMachines);
            });
        }
        public void StartServer(ComponentType componentType, ulong cid, ushort gus, string targetIP, string kbe_root, string kbe_res_path, string kbe_bin_path, int tryCount = 1, int timeout = 1000)
        {
            var bundle = Bundle.createObject();
            bundle.newMessage(FixedMsg.Get("Machine::startserver"));
            bundle.writeInt32(_uid);
            bundle.writeInt32((int)componentType);
            bundle.writeUint64(cid);
            bundle.writeUint16(gus);
            bundle.writeUint16((ushort)IPAddress.HostToNetworkOrder((short)_listenPort));
            bundle.writeString(kbe_root);
            bundle.writeString(kbe_res_path);
            bundle.writeString(kbe_bin_path);

            var endPoint = GetBroadcastEndPoint(targetIP);
            bundle.sendUDP(_udp_broadcast_socket.Client, endPoint);
            ListenReceive(tryCount, timeout, receiveCount: tryCount <= 0 ? 1 : -1);
        }
        public void StopServer(ComponentType componentType, ulong componentID = 0, string targetIP = null, int tryCount = 1, int timeout = 1000)
        {
            var bundle = Bundle.createObject();
            bundle.newMessage(FixedMsg.Get("Machine::stopserver"));
            bundle.writeInt32(_uid);
            bundle.writeInt32((int)componentType);
            bundle.writeUint64(componentID);
            bundle.writeUint16((ushort)IPAddress.HostToNetworkOrder((short)_listenPort));

            var endPoint = GetBroadcastEndPoint(targetIP);
            bundle.sendUDP(_udp_broadcast_socket.Client, endPoint);
            ListenReceive(tryCount, timeout, receiveCount: tryCount <= 0 ? 1 : -1);
        }
        public void KillServer(ComponentType componentType, ulong componentID = 0, string targetIP = null, int tryCount = 1, int timeout = 1000)
        {
            var bundle = Bundle.createObject();
            bundle.newMessage(FixedMsg.Get("Machine::killserver"));
            bundle.writeInt32(_uid);
            bundle.writeInt32((int)componentType);
            bundle.writeUint64(componentID);
            bundle.writeUint16((ushort)IPAddress.HostToNetworkOrder((short)_listenPort));

            var endPoint = GetBroadcastEndPoint(targetIP);
            bundle.sendUDP(_udp_broadcast_socket.Client, endPoint);
            ListenReceive(tryCount, timeout, receiveCount: tryCount <= 0 ? 1 : -1);
        }
        public void SetFlags(ComponentType componentType, uint flags, ulong componentID = 0, string targetIP = null, int tryCount = 1, int timeout = 1000)
        {
            var bundle = Bundle.createObject();
            bundle.newMessage(FixedMsg.Get("Machine::setflags"));
            bundle.writeInt32(_uid);
            bundle.writeInt32((int)componentType);
            bundle.writeUint64(componentID);
            bundle.writeUint32(flags);
            bundle.writeUint16((ushort)IPAddress.HostToNetworkOrder((short)_listenPort));

            var endPoint = GetBroadcastEndPoint(targetIP);
            ListenReceive(tryCount, timeout, (msList)=> {
                var receiveList = new List<int>();
                foreach (var item in msList)
                    receiveList.Add(item.readInt8());
                Logger.Log(string.Format("componentID: {0}, success: {1}", componentID, receiveList.Contains(1)));
            }, receiveCount: tryCount <= 0 ? 1 : -1);
        }
      

        private void parseQueryDatas(List<MemoryStream> memoryStreams)
        {
            foreach (var item in memoryStreams)
            {
                parseQueryData(item);
            }
        }
        private void parseQueryData(MemoryStream recvData)
        {
            var cinfo = new ComponentInfo(recvData);

            if(!interfaces.TryGetValue(cinfo.componentType, out var componentInfos))
            {
                componentInfos = new List<ComponentInfo>();
                interfaces.Add(cinfo.componentType, componentInfos);
            }

            bool found = false;
            foreach (var info in componentInfos)
            {
                if (info.componentID == cinfo.componentID && info.pid == cinfo.pid)
                {
                    found = true;
                    break;
                }    
            }

            if (found)
                return;

            componentInfos.Add(cinfo);
            var machineID = cinfo.machineID;
            if (!interfaces_groups.TryGetValue(machineID, out var gourps))
            {
                gourps = new List<ComponentInfo>();
                interfaces_groups.Add(machineID, gourps);
                interfaces_groups_uid.Add(machineID, new List<int>());
            }

            //如果pid与machineID相等，说明这个是machine进程
            if (cinfo.pid != machineID)
            {
                gourps.Add(cinfo);
                if (!interfaces_groups_uid[machineID].Contains(cinfo.uid))
                    interfaces_groups_uid[machineID].Add(cinfo.uid);
            }
            else
            {
                //是machine进程，把它放在最前面，并且加到machines列表中
                gourps.Insert(0, cinfo);
                machines.Add(cinfo);
            }
        }

        /// <summary>
        /// 生成一个相对唯一的gus（非全局唯一）
        /// </summary>
        public ushort makeGUS(ComponentType componentType)
        {
            if (ct2gus == null)
            {
                ct2gus = new Dictionary<ComponentType, int>();
                foreach (ComponentType item in Enum.GetValues(typeof(ComponentType)))
                {
                    ct2gus.Add(item, 0);
                }
            }

            ct2gus[componentType] += 1;
            return (ushort)((int)componentType * 100 + ct2gus[componentType]);
        }

        /// <summary>
        /// 生成相对唯一的cid（非全局唯一）
        /// </summary>
        public ulong makeCID(ComponentType componentType)
        {
            if (ct2cid == null)
            {
                ct2cid = new Dictionary<ComponentType, int>();
                foreach (ComponentType item in Enum.GetValues(typeof(ComponentType)))
                {
                    ct2cid.Add(item, 0);
                }
            }
            byte[] randomBytes = new byte[4];
            RNGCryptoServiceProvider rngServiceProvider = new RNGCryptoServiceProvider();
            rngServiceProvider.GetBytes(randomBytes);
            var cidRand = BitConverter.ToUInt32(randomBytes, 0);
            ct2cid[componentType] += 1;
            var t = Environment.TickCount % 99999;
            var cidStr = string.Format("{0:02}{1:05}{2:05}{3:04}", (int)componentType, t, cidRand, ct2cid[componentType]);
            int.TryParse(cidStr, out var cid);
            return (ulong)cid;
        }

        /// <summary>
        /// 通过ip地址找到对应的machine的info
        /// </summary>
        public ComponentInfo getMachine(string ip)
        {
            foreach (var info in machines)
            {
                if (info.intaddr.ToString() == ip)
                    return info;
            }
            return null;
        }

        public bool hasMachine(string ip)
        {
            foreach (var info in machines)
            {
                if (info.intaddr.ToString() == ip)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// 获取某一类型的组件信息
        /// </summary>
        public List<ComponentInfo> getComponentInfos(ComponentType componentType)
        {
            if (interfaces.TryGetValue(componentType, out var comList))
                return comList;
            else
                return new List<ComponentInfo>();
        }


        #region 异步等待消息返回
        /// <summary>
        /// 启用异步监听
        /// </summary>
        /// <param name="tryCount">可接受超时次数</param>
        /// <param name="timeout">超时时间</param>
        /// <param name="action">回调</param>
        /// <param name="receiveCount">预计接受次数,如填入大于1的则正确接受到消息后则不继续等待超时</param>
        private void ListenReceive(int tryCount, int timeout = 1000, Action<List<MemoryStream>> action = null, int receiveCount = -1)
        {
            _udp_socket.Client.ReceiveTimeout = timeout;
            ReceiveBytes(new ReceiveState() {
                socket = _udp_socket,
                tryCount = tryCount, 
                action = action,
                activeArg = new List<MemoryStream>(), 
                receiveCount = receiveCount
            });
        }
        private void ReceiveBytes(ReceiveState args)
        {
            Task.Run(() =>
            {
                while (true)
                {
                    try
                    {
                        if (args.receiveCount == 0)
                        {
                            Event.fireIn(_fireReceive, args.action, args.activeArg);
                            return;
                        }
                        if (args.receiveCount != -1)
                            --args.receiveCount;

                        var UdpReceiveResultTask = args.socket.ReceiveAsync();
                        if (UdpReceiveResultTask.Wait(args.socket.Client.ReceiveTimeout))
                        {
                            var result = UdpReceiveResultTask.Result;
                            var newMs = MemoryStream.createObject();
                            newMs.append(result.Buffer, (uint)0, (uint)result.Buffer.Length);
                            args.activeArg.Add(newMs);
                            //receiveList.Add(stream);
                        }
                        else
                        {
                            if (args.receiveCount != -1)
                                ++args.receiveCount;

                            //超时次数到达上限,可以返回了
                            if (args.tryCount <= 0)
                            {
                                Event.fireIn(_fireReceive, args.action, args.activeArg);
                                return;
                            }
                            else
                            {
                                --args.tryCount;
                                continue;
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("连接服务器异常" + ex.Message);
                        break;
                    }
                }
            });
        }
        public void __FireReceive(Action<List<MemoryStream>> action, List<MemoryStream> msList)
        {
            action?.Invoke(msList);
            foreach (var item in msList)
                item.reclaimObject();
        }

        private class ReceiveState
        {
            public UdpClient socket;
            public int tryCount;
            public Action<List<MemoryStream>> action;
            public List<MemoryStream> activeArg;
            public int receiveCount;
        }
        #endregion
    }
}
